/* phone.c */

/********************/
/* SPCORE-TELEPHONE */
/* handling calls   */
/* (C) 2003 by NAH6 */
/********************/

/*------------*/
/*  INCLUDES  */
/*------------*/

#include <memory.h>
#include <windows.h>

#include "crypto.h"
#include "winaudio.h"
#include "winsio.h"
#include "hayes.h"
#include "wintapi.h"
#include "libcodec.h"
#include "codecmux.h"
#include "phone.h"
#include "command.h"

/*--------------*/
/*  PROTOTYPES  */
/*--------------*/

static signed short phone__enter (unsigned char phone, unsigned char section, \
                                  int *prio, struct phone_dseg **dseg,        \
                                  struct phone_protocol **prot);
static void         phone__leave (unsigned char phone, unsigned char section, \
                                  int prio);

static DWORD WINAPI phone__telthread  (LPVOID ptr);
static signed short phone__connect    (struct phone_dseg *dseg);
static void         phone__linkexport (const struct phone_dseg *dseg, \
                                       struct phone_link *usrlink);
static void         phone__linkimport (struct phone_dseg *dseg, \
                                       const struct phone_link *usrlink);

static DWORD WINAPI phone__rxthread   (LPVOID ptr);
static void         phone__rxsyncbyte (struct phone_protocol *prot, \
                                       unsigned char data);
static signed short phone__rxdatabyte (struct phone_dseg *dseg, \
                                       unsigned char data);

static DWORD WINAPI phone__txthread   (LPVOID ptr);
static signed short phone__txchunk    (struct sio_data *sio,        \
                                       struct phone_protocol *prot, \
                                       unsigned char len);
static signed short phone__txputch    (struct sio_data *sio, \
                                       unsigned char data);

/*-------------*/
/*  VARIABLEN  */
/*-------------*/

struct phone_baseinfo phone_base[K_PHONE_MAXPHONES];

unsigned short jitter;
#include "wintop.h"
/*============================================================================*/
/*              C  P  H  O  N  E   -   T  E  L  E  P  H  O  N  E              */
/*============================================================================*/

/*-----------------*/
/*  PHONE-INSTALL  */
/*-----------------*/
                                                                    /* [1101] */
void phone_install (void)                                   /* Install module */
{
  struct phone_baseinfo *p;
  unsigned char i;

  p = phone_base;
  for (i=0; i<K_PHONE_MAXPHONES; i++)
  {
    memset (&(p->settings), 0, sizeof (struct phone_config));
    p->dseg = NULL;
    InitializeCriticalSection (&(p->slock));
    InitializeCriticalSection (&(p->dlock));
    p++;
  }
}

/*----*/
                                                                    /* [1102] */
void phone_remove (void)                                     /* Remove module */
{
  struct phone_baseinfo *p;
  unsigned char i;

  p = phone_base + K_PHONE_MAXPHONES - 1;
  for (i=0; i<K_PHONE_MAXPHONES; i++)
  {
    DeleteCriticalSection (&(p->dlock));
    DeleteCriticalSection (&(p->slock));
    p--;
  }
}

/*----------------------------------------------------------------------------*/

/*----------------*/
/*  PHONE-CONFIG  */
/*----------------*/

void phone_hwdetect_create (struct phone_hwinfo *info)              /* [1103] */
{                                                    /* Detect Phone-Hardware */
  audio_hwdetect (&(info->ainfo));
  tapi_hwdetect_create (&(info->tinfo));
}

void phone_hwdetect_destroy (const struct phone_hwinfo *info)       /* [1104] */
{                                                   /* Destroy Phone-Hardware */
  tapi_hwdetect_destroy (&(info->tinfo));
}

/*====*/

signed short phone_autoconfig (unsigned char phone, unsigned char cfgtype,
							   unsigned char mdmport, unsigned char mdmtype)  /* [1105] */
{                                                              /* Auto config */
  struct phone_baseinfo *base;
  struct phone_hwinfo *info;
  struct phone_config *cfg;
  HANDLE heap;

  /* Check parameter */
  if (phone >= K_PHONE_MAXPHONES)
    return E_PHONE_BADPHONE;
  base = phone_base + phone;
  cfg = &(base->settings);

  /* Create Hw-Info */
  heap = GetProcessHeap ();
  info = HeapAlloc (heap, 0L, sizeof (struct phone_hwinfo));
  if (info == NULL)
    return E_PHONE_NOMEM;
  phone_hwdetect_create (info);

  /* Create Hw-Config */
  EnterCriticalSection (&(base->slock));
  audio_hwautocfg (&(cfg->acfg), &(info->ainfo));

  switch (cfgtype)
  {
    case CFGTYPE_DESKTOP:

  cfg->ltype = K_PHONE_LINETYPE_HAYES;
  cfg->lcfg.hayes.sigterm             = NULL;
  cfg->lcfg.hayes.sio_portnum         = mdmport;
  cfg->lcfg.hayes.sio_obxport         = 0;
  cfg->lcfg.hayes.sio_rilport         = 0;
  cfg->lcfg.hayes.sio_baudrate        = K_SIO_BAUDRATE_19200;
  cfg->lcfg.hayes.sio_flowctrl        = K_SIO_FLOWCTRL_RTSCTS;
  cfg->lcfg.hayes.sio_usedtr          = K_SIO_DTRUSE_NONE;
  cfg->lcfg.hayes.sio_usedsr          = 1;
  cfg->lcfg.hayes.sio_usedcd          = 1;
  cfg->lcfg.hayes.sio_usering         = 0;
  cfg->lcfg.hayes.modem_type          = mdmtype; // K_HAYES_MODEMTYPE_POTS, K_HAYES_MODEMTYPE_GSM_SIE;
  cfg->lcfg.hayes.modem_pots_volume   = 0xFF;
  cfg->lcfg.hayes.modem_pots_tonedial = 1;
  cfg->lcfg.hayes.modem_pots_dialtone = 1;
  cfg->lcfg.hayes.modem_pots_busytone = 1;
  memset (cfg->lcfg.hayes.modem_initstr, 0, K_HAYES_INITSTRBUFLEN);
  memset (cfg->lcfg.hayes.modem_dialpfx, 0, K_HAYES_DIALPFXBUFLEN);
    break;
    case CFGTYPE_GSMTAPI:  /* GSM via Tapi */
  cfg->ltype = K_PHONE_LINETYPE_TAPI;
  tapi_hwautocfg (&(cfg->lcfg.tcfg.tapi), &(info->tinfo));
  cfg->lcfg.tcfg.sio.sigterm  = NULL;
  cfg->lcfg.tcfg.sio.portnum  = K_SIO_PORTNUM_HANDLE;
  cfg->lcfg.tcfg.sio.porthnd  = INVALID_HANDLE_VALUE;
  cfg->lcfg.tcfg.sio.obxport  = 0;
  cfg->lcfg.tcfg.sio.rilport  = 0;
  cfg->lcfg.tcfg.sio.baudrate = K_SIO_BAUDRATE_9600;
  cfg->lcfg.tcfg.sio.flowctrl = K_SIO_FLOWCTRL_NONE;
  cfg->lcfg.tcfg.sio.usedtr   = K_SIO_DTRUSE_NONE;
  cfg->lcfg.tcfg.sio.usedsr   = 0;
  cfg->lcfg.tcfg.sio.usedcd   = 0;
  cfg->lcfg.tcfg.sio.usering  = 0;
    break;
    case CFGTYPE_GSMHAYES_COM2: /* GSM via Hayes an COM2 */
  cfg->ltype = K_PHONE_LINETYPE_HAYES;
  cfg->lcfg.hayes.sigterm             = NULL;
  cfg->lcfg.hayes.sio_portnum         = 1;
  cfg->lcfg.hayes.sio_obxport         = 0;
  cfg->lcfg.hayes.sio_rilport         = 1;
  cfg->lcfg.hayes.sio_baudrate        = K_SIO_BAUDRATE_115200;
  cfg->lcfg.hayes.sio_flowctrl        = K_SIO_FLOWCTRL_NONE;
  cfg->lcfg.hayes.sio_usedtr          = K_SIO_DTRUSE_NONE;
  cfg->lcfg.hayes.sio_usedsr          = 0;
  cfg->lcfg.hayes.sio_usedcd          = 0;
  cfg->lcfg.hayes.sio_usering         = 0;
  cfg->lcfg.hayes.modem_type          = K_HAYES_MODEMTYPE_GSM;
  cfg->lcfg.hayes.modem_pots_volume   = 0xFF;
  cfg->lcfg.hayes.modem_pots_tonedial = 1;
  cfg->lcfg.hayes.modem_pots_dialtone = 1;
  cfg->lcfg.hayes.modem_pots_busytone = 1;
  memset (cfg->lcfg.hayes.modem_initstr, 0, K_HAYES_INITSTRBUFLEN);
  memset (cfg->lcfg.hayes.modem_dialpfx, 0, K_HAYES_DIALPFXBUFLEN);
    break;
    case CFGTYPE_POTSHAYES_COM1: /* POTS via Hayes an COM1 */
  cfg->ltype = K_PHONE_LINETYPE_HAYES;
  cfg->lcfg.hayes.sigterm             = NULL;
  cfg->lcfg.hayes.sio_portnum         = 0;
  cfg->lcfg.hayes.sio_obxport         = 1;
  cfg->lcfg.hayes.sio_rilport         = 0;
  cfg->lcfg.hayes.sio_baudrate        = K_SIO_BAUDRATE_115200;
  cfg->lcfg.hayes.sio_flowctrl        = K_SIO_FLOWCTRL_RTSCTS;
  cfg->lcfg.hayes.sio_usedtr          = K_SIO_DTRUSE_REOPEN;
  cfg->lcfg.hayes.sio_usedsr          = 0;
  cfg->lcfg.hayes.sio_usedcd          = 1;
  cfg->lcfg.hayes.sio_usering         = 0;
  cfg->lcfg.hayes.modem_type          = mdmtype;  // K_HAYES_MODEMTYPE_POTS_CIR;
  cfg->lcfg.hayes.modem_pots_volume   = 0x40;
  cfg->lcfg.hayes.modem_pots_tonedial = 1;
  cfg->lcfg.hayes.modem_pots_dialtone = 1;
  cfg->lcfg.hayes.modem_pots_busytone = 1;
  memset (cfg->lcfg.hayes.modem_initstr, 0, K_HAYES_INITSTRBUFLEN);
  memset (cfg->lcfg.hayes.modem_dialpfx, 0, K_HAYES_DIALPFXBUFLEN);
    break;
  }
  LeaveCriticalSection (&(base->slock));

  /* Destroy Hw-Info */
  phone_hwdetect_destroy (info);
  HeapFree (heap, 0L, info);
  return 0;
}

/*----*/

signed short phone_setconfig (unsigned char phone, \
                              const struct phone_config *config)    /* [1106] */
{                                                             /* Write config */
  struct phone_baseinfo *base;

  /* Check parameter */
  if (phone >= K_PHONE_MAXPHONES)
    return E_PHONE_BADPHONE;
  if (config == NULL)
    return E_PHONE_NULLPTR;
  base = phone_base + phone;

  /* Copy config */
  EnterCriticalSection (&(base->slock));
  memcpy (&(base->settings), config, sizeof (struct phone_config));
  LeaveCriticalSection (&(base->slock));
  return 0;
}

/*----*/

signed short phone_getconfig (unsigned char phone, \
                              struct phone_config *config)          /* [1107] */
{                                                              /* Read config */
  struct phone_baseinfo *base;

  /* Check parameter */
  if (phone >= K_PHONE_MAXPHONES)
    return E_PHONE_BADPHONE;
  if (config == NULL)
    return E_PHONE_NULLPTR;
  base = phone_base + phone;

  /* Copy config */
  EnterCriticalSection (&(base->slock));
  memcpy (config, &(base->settings), sizeof (struct phone_config));
  LeaveCriticalSection (&(base->slock));
  return 0;
}

/*----------------*/
/*  PHONE-ON/OFF  */
/*----------------*/

signed short phone_on (unsigned char phone)                         /* [1108] */
{                                                             /* Enable Phone */
  struct phone_baseinfo *base;
  struct phone_dseg *dseg;
  signed short n;
  HANDLE heap;

  /* Check parameter */
  if (phone >= K_PHONE_MAXPHONES)
    return E_PHONE_BADPHONE;
  base = phone_base + phone;

  /* Start phone */
  n = 1;
  EnterCriticalSection (&(base->dlock));
  if (base->dseg == NULL)
  {
    /* Allocate memory */
    heap = GetProcessHeap ();
    dseg = HeapAlloc (heap, 0L, sizeof (struct phone_dseg));
    if (dseg == NULL)
    {
      n = E_PHONE_NOMEM;
      goto err_exit2;
    }
    /* Init data */
    dseg->phone   = phone;
    dseg->sigterm = 0;
    dseg->sighup  = 0;
    dseg->busy    = 0;
    dseg->ringing = 0;
    EnterCriticalSection (&(base->slock));
    memcpy (&(dseg->config), &(base->settings), sizeof (struct phone_config));
    LeaveCriticalSection (&(base->slock));
    if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
      dseg->config.lcfg.hayes.sigterm = &(dseg->sighup);
    else
    {
      dseg->config.lcfg.tcfg.tapi.sigterm = &(dseg->sighup);
      dseg->config.lcfg.tcfg.sio.sigterm  = &(dseg->sighup);
      dseg->config.lcfg.tcfg.sio.portnum  = K_SIO_PORTNUM_HANDLE;
      dseg->config.lcfg.tcfg.sio.porthnd  = INVALID_HANDLE_VALUE;
      dseg->config.lcfg.tcfg.sio.obxport  = 0;
      dseg->config.lcfg.tcfg.sio.rilport  = 0;
    }
    /* Install audio */
    n = audio_install (&(dseg->adseg), &(dseg->config.acfg));
    if (n < 0)
      goto err_exit1;
    /* Create thread */
    dseg->telthread = CreateThread (NULL, 0, phone__telthread, dseg, \
                                    CREATE_SUSPENDED, &(dseg->telthreadid));
    if (dseg->telthread != NULL)
    {
      SetThreadPriority (dseg->telthread, K_PHONE_TELPRIO);
      InitializeCriticalSection (&(dseg->plock[0]));
      InitializeCriticalSection (&(dseg->plock[1]));
      dseg->proto = NULL;
      base->dseg  = dseg;
      ResumeThread (dseg->telthread);
      n = 0;
    }
    else
    {
      audio_remove (&(dseg->adseg));
      n = E_PHONE_TELTHREAD;
      err_exit1:
      HeapFree (heap, 0L, dseg);
    }
  }
  err_exit2:
  LeaveCriticalSection (&(base->dlock));
  return n;
}

/*----*/

signed short phone_off (unsigned char phone, signed short *retval)  /* [1109] */
{                                                            /* Disable Phone */
  struct phone_baseinfo *base;
  struct phone_dseg *dseg;
  signed short n;
  HANDLE heap;
  DWORD d;

  /* Check parameter */
  if (phone >= K_PHONE_MAXPHONES)
    return E_PHONE_BADPHONE;
  base = phone_base + phone;

  /* Stop phone */
  n = 0;
  if (retval != NULL)
    *retval = 0;
  EnterCriticalSection (&(base->dlock));
  dseg = base->dseg;
  if (dseg != NULL)
  {
    /* Stop thread */
    dseg->sigterm = 1;
    dseg->sighup  = 1;
    WaitForSingleObject (dseg->telthread, INFINITE);
    base->dseg = NULL;
    DeleteCriticalSection (&(dseg->plock[1]));
    DeleteCriticalSection (&(dseg->plock[0]));
    GetExitCodeThread (dseg->telthread, &d);
    if (retval != NULL)
      *retval = (signed short)d;
    CloseHandle (dseg->telthread);
    /* Remove audio */
    n = audio_remove (&(dseg->adseg));
    /* Free memory */
    memset (dseg, 0, sizeof (struct phone_dseg));
    heap = GetProcessHeap ();
    HeapFree (heap, 0L, dseg);
    if (n >= 0)
      n = 1;
  }
  LeaveCriticalSection (&(base->dlock));
  return n;
}

/*----------------------------------------------------------------------------*/

/*-----------------*/
/*  PHONE-CONNECT  */
/*-----------------*/

signed short phone_hangup (unsigned char phone)                     /* [1110] */
{                                                              /* Hangup Call */
  struct phone_baseinfo *base;
  struct phone_dseg *dseg;
  signed short n;

  /* Check parameter */
  if (phone >= K_PHONE_MAXPHONES)
    return E_PHONE_BADPHONE;
  base = phone_base + phone;

  /* Stop call */
  n = E_PHONE_ISOFF;
  EnterCriticalSection (&(base->dlock));
  dseg = base->dseg;
  if (dseg != NULL)
  {
    /* Raise sighup */
    dseg->sighup = 1;
    n = 0;
  }
  LeaveCriticalSection (&(base->dlock));
  return n;
}

/*----*/

signed short phone_connect (unsigned char phone, const struct phone_link *link)
{                                                                   /* [1111] */
  struct phone_baseinfo *base;                                /* Connect Call */
  struct phone_dseg *dseg;
  signed short n;

  /* Check parameter */
  if (phone >= K_PHONE_MAXPHONES)
    return E_PHONE_BADPHONE;
  if (link == NULL)
    return E_PHONE_NULLPTR;
  base = phone_base + phone;

  /* Connect call */
  n = E_PHONE_ISOFF;
  EnterCriticalSection (&(base->dlock));
  dseg = base->dseg;
  if (dseg != NULL)
  {
    n = E_PHONE_INUSE;
    if (!(dseg->busy))
    {
      memcpy (&(dseg->usrlink), link, sizeof (struct phone_link));
      dseg->busy = 1;
      n = 0;
    }
  }
  LeaveCriticalSection (&(base->dlock));
  return n;
}

/*-----------------*/
/*  PHONE-CONTROL  */
/*-----------------*/

signed short phone_setdecstate (unsigned char phone,         \
                                const unsigned char key[32], \
                                const unsigned char cnt[16])        /* [1120] */
{                                                        /* Set Decoder-State */
  struct phone_protocol *prot;
  struct phone_dseg *dseg;
  unsigned char i,*p;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 0, &prio, &dseg, &prot);
  if (n < 0)
    return n;

  /* Set decoder state */
  prot->rxchunklen = 0;
  prot->rxudclen   = 0;
  if (prot->rxsyncstat)
  {
    prot->rxsyncstat = 0;
    cmd_notify (dseg->phone, K_CMD_NOTIFY_RXSYNC, &(prot->rxsyncstat), 0);
  }
  prot->rxsyncidx = 0;
  prot->rxsyncesc = 0;
  if (key != NULL)
  {
    p = (unsigned char *)(prot->rxcryptkey);
    for (i=0; i<32; i++)
      *p++ = *key++;
    crypto_tandem_setup (&(prot->rxcrypt), prot->rxcryptkey);
  }
  if (cnt != NULL)
  {
    p = (unsigned char *)(prot->rxcryptcnt);
    for (i=0; i<16; i++)
      *p++ = *cnt++;
  }
  p = (unsigned char *)(prot->rxcryptcnt);
  *p &= 0xFC;
  phone__leave (phone, 0, prio);
  return 0;
}


signed short phone_getdecstate (unsigned char phone, unsigned char key[32], \
                                unsigned char cnt[16])              /* [1121] */
{                                                        /* Get Decoder-State */
  struct phone_protocol *prot;
  const unsigned char *p;
  unsigned char i;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 0, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get decoder state */
  if (key != NULL)
  {
    p = (const unsigned char *)(prot->rxcryptkey);
    for (i=0; i<32; i++)
      *key++ = *p++;
  }
  if (cnt != NULL)
  {
    p = (const unsigned char *)(prot->rxcryptcnt);
    for (i=0; i<16; i++)
      *cnt++ = *p++;
  }
  phone__leave (phone, 0, prio);
  return 0;
}

/*----*/

signed short phone_setdecmode (unsigned char phone, unsigned char mode)
{                                                                   /* [1122] */
  struct phone_protocol *prot;                            /* Set Decoder-Mode */
  struct phone_dseg *dseg;
  unsigned char x;
  signed short n;
  int prio;

  /* Check parameter */
  if (mode > 1)
    return E_PHONE_MODE;
  n = phone__enter (phone, 0, &prio, &dseg, &prot);
  if (n < 0)
    return n;

  /* Set decoder mode */
  n = 0;
  x = prot->rxmode;
  if (!x && mode)
  { /* switch on */
    n = audio_outclear (&(dseg->adseg), 0);
    if (n >= 0)
      prot->rxmode = 1;
  }
  else if (x && !mode)
  { /* switch off */
    n = audio_outclear (&(dseg->adseg), 0);
    prot->rxmode = 0;
  }
  phone__leave (phone, 0, prio);
  if (n >= 0)
    n = x;
  return n;
}


signed short phone_getdecmode (unsigned char phone)                 /* [1123] */
{                                                         /* Get Decoder-Mode */
  struct phone_protocol *prot;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 0, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get decoder mode */
  n = prot->rxmode;
  phone__leave (phone, 0, prio);
  return n;
}


signed short phone_setdeccodectbl (unsigned char phone, unsigned char index, \
                                   unsigned char codec)             /* [1124] */
{                                                       /* Set Rx-Codec-Table */
  struct phone_protocol *prot;
  signed short n;
  int prio;

  /* Check parameter */
  if (index > 1)
    return E_PHONE_INDEX;
  if (codec >= K_CODECMUX_CODECS)
    return E_PHONE_CODEC;
  n = phone__enter (phone, 0, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Set decoder codec table */
  n = prot->rxcodectbl[index];
  prot->rxcodectbl[index] = codec;
  phone__leave (phone, 0, prio);
  return n;
}


signed short phone_getdeccodectbl (unsigned char phone, unsigned char codec[2])
{                                                                   /* [1125] */
  struct phone_protocol *prot;                          /* Get Rx-Codec-Table */
  signed short n;
  int prio;

  /* Check parameter */
  if (codec == NULL)
    return E_PHONE_NULLPTR;
  n = phone__enter (phone, 0, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get decoder codec table */
  codec[0] = prot->rxcodectbl[0];
  codec[1] = prot->rxcodectbl[1];
  phone__leave (phone, 0, prio);
  return 0;
}


signed short phone_getdeccodec (unsigned char phone)                /* [1126] */
{                                                             /* Get Rx-Codec */
  struct phone_protocol *prot;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 0, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get decoder codec */
  n = prot->rxcodecsel;
  phone__leave (phone, 0, prio);
  return n;
}

/*----*/

signed short phone_getdecsync (unsigned char phone)                 /* [1127] */
{                                                              /* Get Rx-Sync */
  struct phone_protocol *prot;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 0, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get decoder sync */
  n = prot->rxsyncstat;
  phone__leave (phone, 0, prio);
  return n;
}

/*====*/

signed short phone_setencstate (unsigned char phone,         \
                                const unsigned char key[32], \
                                const unsigned char cnt[16])        /* [1130] */
{                                                        /* Set Encoder-State */
  struct phone_protocol *prot;
  unsigned char i,x,y,*p;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 1, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Set encoder state */
  prot->txudcrdidx = 0;
  if (key != NULL)
  {
    p = (unsigned char *)(prot->txcryptkey);
    for (i=0; i<32; i++)
      *p++ = *key++;
    crypto_tandem_setup (&(prot->txcrypt), prot->txcryptkey);
  }
  if (cnt != NULL)
  {
    p = (unsigned char *)(prot->txcryptcnt);
    for (i=0; i<16; i++)
      *p++ = *cnt++;
  }
  x = 3;
  p = (unsigned char *)(prot->txcryptcnt);
  for (i=0; i<16; i++)
  {
    y = *p;
    *p += x;
    if (*p++ >= y)
      break;
    x = 1;
  }
  p = (unsigned char *)(prot->txcryptcnt);
  *p &= 0xFC;
  prot->txcryptbufpos = 0;
  crypto_tandem_encrypt (&(prot->txcrypt), prot->txcryptcnt, prot->txcryptbuf);
  phone__leave (phone, 1, prio);
  return 0;
}


signed short phone_getencstate (unsigned char phone, unsigned char key[32], \
                                unsigned char cnt[16])              /* [1131] */
{                                                        /* Get Encoder-State */
  struct phone_protocol *prot;
  const unsigned char *p;
  unsigned char i;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 1, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get encoder state */
  if (key != NULL)
  {
    p = (const unsigned char *)(prot->txcryptkey);
    for (i=0; i<32; i++)
      *key++ = *p++;
  }
  if (cnt != NULL)
  {
    p = (const unsigned char *)(prot->txcryptcnt);
    for (i=0; i<16; i++)
      *cnt++ = *p++;
  }
  phone__leave (phone, 1, prio);
  return 0;
}

/*----*/

signed short phone_setencmode (unsigned char phone, unsigned char mode)
{                                                                   /* [1132] */
  struct phone_protocol *prot;                            /* Set Encoder-Mode */
  struct phone_dseg *dseg;
  unsigned char x;
  signed short n;
  int prio;

  /* Check parameter */
  if (mode > 1)
    return E_PHONE_MODE;
  n = phone__enter (phone, 1, &prio, &dseg, &prot);
  if (n < 0)
    return n;

  /* Set encoder mode */
  n = 0;
  x = prot->txmode;
  if (!x && mode)
  { /* switch on */
    n = audio_inclear (&(dseg->adseg));
    if (n >= 0)
      n = audio_instart (&(dseg->adseg));
    if (n >= 0)
      prot->txmode = 1;
  }
  else if (x && !mode)
  { /* switch off */
    n = audio_instop  (&(dseg->adseg));
    if (n >= 0)
      n = audio_inclear (&(dseg->adseg));
    prot->txmode = 0;
  }
  phone__leave (phone, 1, prio);
  if (n >= 0)
    n = x;
  return n;
}


signed short phone_getencmode (unsigned char phone)                 /* [1133] */
{                                                         /* Get Encoder-Mode */
  struct phone_protocol *prot;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 1, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get encoder mode */
  n = prot->txmode;
  phone__leave (phone, 1, prio);
  return n;
}


signed short phone_setenccodectbl (unsigned char phone, unsigned char index, \
                                   unsigned char codec)             /* [1134] */
{                                                       /* Set Tx-Codec-Table */
  struct phone_protocol *prot;
  signed short n;
  int prio;

  /* Check parameter */
  if (index > 1)
    return E_PHONE_INDEX;
  if (codec >= K_CODECMUX_CODECS)
    return E_PHONE_CODEC;
  n = phone__enter (phone, 1, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Set encoder codec table */
  n = prot->txcodectbl[index];
  prot->txcodectbl[index] = codec;
  phone__leave (phone, 1, prio);
  return n;
}


signed short phone_getenccodectbl (unsigned char phone, unsigned char codec[2])
{                                                                   /* [1135] */
  struct phone_protocol *prot;                          /* Get Tx-Codec-Table */
  signed short n;
  int prio;

  /* Check parameter */
  if (codec == NULL)
    return E_PHONE_NULLPTR;
  n = phone__enter (phone, 1, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get encoder codec table */
  codec[0] = prot->txcodectbl[0];
  codec[1] = prot->txcodectbl[1];
  phone__leave (phone, 1, prio);
  return 0;
}


signed short phone_setenccodec (unsigned char phone, unsigned char index)
{                                                                   /* [1136] */
  struct phone_protocol *prot;                                /* Set Tx-Codec */
  signed short n;
  int prio;

  /* Check parameter */
  if (index > 1)
    return E_PHONE_INDEX;
  n = phone__enter (phone, 1, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Set encoder codec */
  n = prot->txcodecsel;
  prot->txcodecsel = index;
  phone__leave (phone, 1, prio);
  return n;
}


signed short phone_getenccodec (unsigned char phone)                /* [1137] */
{                                                             /* Get Tx-Codec */
  struct phone_protocol *prot;
  signed short n;
  int prio;

  /* Check parameter */
  n = phone__enter (phone, 1, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Get encoder codec */
  n = prot->txcodecsel;
  phone__leave (phone, 1, prio);
  return n;
}

/*----*/

signed short phone_userdata (unsigned char phone, unsigned long id, \
                             const void *data, unsigned short len)  /* [1138] */
{                                                           /* Send User-Data */
  struct phone_protocol *prot;
  unsigned char x,*p;
  signed short n;
  int prio;

  /* Check parameter */
  if (data == NULL)
    return E_PHONE_NULLPTR;
  if ((!len) || (len > K_PHONE_UDCMSGLEN))
    return E_PHONE_SIZE;
  n = phone__enter (phone, 1, &prio, NULL, &prot);
  if (n < 0)
    return n;

  /* Send user-data */
  n = E_PHONE_FULL;
  x = prot->txudcwrpos;
  if ((x != prot->txudcrdpos) || !(prot->txudcdada))
  {
    prot->txudcid [x] = id;
    prot->txudclen[x] = len + 4;
    p = prot->txudcbuf[x];
    memcpy (p, data, len);
    crypto_sha_setup (&(prot->txhash));
    crypto_sha_add   (&(prot->txhash), p, len);
    crypto_sha_final (&(prot->txhash), p + len, 4);
    if (x < (K_PHONE_UDCMSGCNT - 1))
      x++;
    else
      x = 0;
    prot->txudcwrpos = x;
    prot->txudcdada = 1;
    SetEvent (prot->txudcevt);
    n = 0;
  }
  phone__leave (phone, 1, prio);
  return n;
}

/*====*/

static signed short phone__enter (unsigned char phone, unsigned char section, \
                                  int *prio, struct phone_dseg **dseg,        \
                                  struct phone_protocol **prot)
{                                                           /* Enter Protocol */
  struct phone_baseinfo *base;
  signed short n;
  HANDLE thnd;

  /* Check parameter */
  if (phone >= K_PHONE_MAXPHONES)
    return E_PHONE_BADPHONE;
  base = phone_base + phone;

  /* Enter section */
  thnd = GetCurrentThread ();
  *prio = GetThreadPriority (thnd);
  EnterCriticalSection (&(base->dlock));
  if (base->dseg == NULL)
  {
    n = E_PHONE_ISOFF;
    goto err_exit;
  }
  if (dseg != NULL)
    *dseg = base->dseg;
  SetThreadPriority (thnd, K_PHONE_DSPPRIO);
  EnterCriticalSection (&(base->dseg->plock[section]));
  if (base->dseg->proto == NULL)
  {
    n = E_PHONE_NOCALL;
    LeaveCriticalSection (&(base->dseg->plock[section]));
    SetThreadPriority (thnd, *prio);
    err_exit:
    LeaveCriticalSection (&(base->dlock));
    return n;
  }
  if (prot != NULL)
    *prot = base->dseg->proto;
  return 0;
}


static void phone__leave (unsigned char phone, unsigned char section, int prio)
{                                                           /* Leave Protocol */
  struct phone_baseinfo *base;
  HANDLE thnd;

  /* Leave section */
  base = phone_base + phone;
  LeaveCriticalSection (&(base->dseg->plock[section]));
  thnd = GetCurrentThread ();
  SetThreadPriority (thnd, prio);
  LeaveCriticalSection (&(base->dlock));
}

/*============================================================================*/

/*----------------*/
/*  PHONE-THREAD  */
/*----------------*/

static DWORD WINAPI phone__telthread (LPVOID ptr)         /* Telephone Thread */
{
  struct phone_dseg *dseg;
  signed short n,m;
  unsigned char c;
  DWORD d;

  /* Install phone */
  dseg = ptr;
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
    n = hayes_open (&(dseg->ldseg.hayes), &(dseg->config.lcfg.hayes));
  else
    n = tapi_open (&(dseg->ldseg.tram.tapi), &(dseg->config.lcfg.tcfg.tapi));
  if (n < 0)
    return n;

  /* Main job */
  c = 0;
  while (42)
  {
    n = 0;
    dseg->sighup = 0;
    if (dseg->sigterm)
      break;
    if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
    {
      n = hayes_watch (&(dseg->ldseg.hayes), &(dseg->link.hayes), c);
      if ((n < 0) && (n != E_SIO_LINE_SIGTERM))
        break;
    }
    else
    {
      n = tapi_watch (&(dseg->ldseg.tram.tapi), &(dseg->link.tapi));
      if ((n < 0) && (n != E_TAPI_BREAK))
        break;
    }
    c = 0;
    if (((dseg->config.ltype == K_PHONE_LINETYPE_HAYES) \
      && (n > 0))                                       \
     || ((dseg->config.ltype == K_PHONE_LINETYPE_TAPI)  \
      && (n == K_TAPI_MSG_RINGING)))    /* Ringing */
    {
      if (!dseg->ringing)
        goto ring_on;
      dseg->lastring = GetTickCount ();
      dseg->evtlink.jobid = 0xFFFFFFFFL;
      phone__linkexport (dseg, &(dseg->evtlink));
      cmd_notify (dseg->phone, K_CMD_NOTIFY_RING, &(dseg->evtlink), 0);
    }
    else if ((dseg->config.ltype == K_PHONE_LINETYPE_TAPI) \
          && (n == K_TAPI_MSG_RINGON))  /* Ringon */
    {
      ring_on:
      dseg->ringing = 1;
      dseg->lastring = GetTickCount ();
      dseg->evtlink.jobid = 0xFFFFFFFFL;
      phone__linkexport (dseg, &(dseg->evtlink));
      cmd_notify (dseg->phone, K_CMD_NOTIFY_RINGON, &(dseg->evtlink), 0);
      cmd_notify (dseg->phone, K_CMD_NOTIFY_RING, &(dseg->evtlink), 0);
    }
    else if ((n == K_TAPI_MSG_NONE) && (dseg->ringing))
    {                                   /* Ring-Timer */
      d = GetTickCount ();
      d -= dseg->lastring;
      if ((d >= K_PHONE_RINGTIMEOUT) || (d < 0))
        goto ring_off;
    }
    else if ((dseg->config.ltype == K_PHONE_LINETYPE_TAPI) \
          && (n == K_TAPI_MSG_RINGOFF)) /* Ringoff */
    {
      ring_off:
      dseg->evtlink.jobid = 0xFFFFFFFFL;
      phone__linkexport (dseg, &(dseg->evtlink));
      cmd_notify (dseg->phone, K_CMD_NOTIFY_RINGOFF, &(dseg->evtlink), 0);
      dseg->ringing = 0;
      c = 1;
    }
    if (dseg->busy)
    {
      /* Connect job */
      if (dseg->ringing)
      {
        dseg->evtlink.jobid = 0xFFFFFFFFL;
        phone__linkexport (dseg, &(dseg->evtlink));
        cmd_notify (dseg->phone, K_CMD_NOTIFY_RINGOFF, &(dseg->evtlink), 0);
        dseg->ringing = 0;
      }
      phone__linkimport (dseg, &(dseg->usrlink));
      cmd_notify (dseg->phone, K_CMD_NOTIFY_CONNECT_0, &(dseg->usrlink), 0);
      n = phone__connect (dseg);
      memcpy (&(dseg->evtlink), &(dseg->usrlink), sizeof (struct phone_link));
      dseg->busy = 0;
      cmd_notify (dseg->phone, K_CMD_NOTIFY_FINAL, &(dseg->evtlink), n);
      c = 1;
    }
  }

  /* Remove phone */
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
    m = hayes_close (&(dseg->ldseg.hayes));
  else
    m = tapi_close (&(dseg->ldseg.tram.tapi));
  if ((m < 0) && (n >= 0))
    n = m;
  return n;
}

/*--------------*/
/*  PHONE-JOBS  */
/*--------------*/

static signed short phone__connect (struct phone_dseg *dseg)  /* Connect call */
{
  struct phone_protocol *prot;
  struct audio_openopt opt;
  struct sio_data *sio;
  HANDLE heap,har[2];
  unsigned long *p;
  signed short n,m;
  DWORD d,e;

  /* Establish call */
#ifdef _WIN32_WCE
  SystemIdleTimerReset ();
#endif
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
    n = hayes_connect (&(dseg->ldseg.hayes), &(dseg->link.hayes));
  else
    n = tapi_connect (&(dseg->ldseg.tram.tapi), &(dseg->link.tapi));
  if (n < 0)
    return n;

  /* Open audio */
  opt.bufferlen   = 40000L;
  opt.triggerlvl  = 80;
  opt.samplerate  = K_FILTER_SAMPLERATE_11025;
#ifndef _WIN32_WCE
  opt.winchunks   = 6;
#else
  opt.winchunks   = 4;
#endif
  opt.sound       = NULL;
  n = audio_open (&(dseg->adseg), &opt);
  if (n < 0)
    goto err_exit9;
  n = audio_instop  (&(dseg->adseg));
  if (n < 0)
    goto err_exit8;
  n = audio_inclear (&(dseg->adseg));
  if (n < 0)
    goto err_exit8;
  n = audio_outclear (&(dseg->adseg), 0);
  if (n < 0)
    goto err_exit8;

  /* Setup protocol */
  heap = GetProcessHeap ();
  prot = HeapAlloc (heap, 0L, sizeof (struct phone_protocol));
  if (prot == NULL)
  {
    n = E_PHONE_NOMEM;
    goto err_exit8;
  }
  prot->rxmode        = 0;/* RX */
  prot->rxcodecsel    = 0;
  prot->rxcodectbl[0] = K_CODECMUX_CODEC_CELP;
  prot->rxcodectbl[1] = K_CODECMUX_CODEC_CELP;
  prot->rxcodecopen   = K_CODECMUX_CODEC_CELP;
  prot->rxchunklen    = 0;
  prot->rxjitval      = K_PHONE_STDJITTER;
  prot->rxjitcnt      = 0;
  prot->rxjitmax      = 0;
  prot->rxjittime     = GetTickCount () - K_PHONE_MAXJITTER - 1;
  prot->rxudclen      = 0;
  prot->rxsyncstat    = 0;
  prot->rxsyncidx     = 0;
  prot->rxsyncesc     = 0;
  p = prot->rxcryptkey;
  for (n=0; n<8; n++)
    *p++ = 0L;
  p = prot->rxcryptcnt;
  for (n=0; n<4; n++)
    *p++ = 0L;
  crypto_tandem_setup (&(prot->rxcrypt), prot->rxcryptkey);
  prot->txmode        = 0;/* TX */
  prot->txcodecsel    = 0;
  prot->txcodectbl[0] = K_CODECMUX_CODEC_CELP;
  prot->txcodectbl[1] = K_CODECMUX_CODEC_CELP;
  prot->txcodecopen   = K_CODECMUX_CODEC_CELP;
  prot->txudcevt = CreateEvent (NULL, TRUE, 0, NULL);
  if (prot->txudcevt == NULL)
  {
    n = E_PHONE_TXEVENT;
    goto err_exit7;
  }
  prot->txudcdada  = 0;
  prot->txudcwrpos = 0;
  prot->txudcrdpos = 0;
  prot->txudcrdidx = 0;
  p = prot->txcryptkey;
  for (n=0; n<8; n++)
    *p++ = 0L;
  p = prot->txcryptcnt;
  for (n=0; n<4; n++)
    *p++ = 0L;
  prot->txcryptbufpos = 0;
  crypto_tandem_setup (&(prot->txcrypt), prot->txcryptkey);
  crypto_tandem_encrypt (&(prot->txcrypt), prot->txcryptcnt, prot->txcryptbuf);

  /* Open codec */
  n = codec_dec_create (K_CODECMUX_CODEC_CELP, &(prot->rxcodecstate));
  if (n < 0)
    goto err_exit6;
  n = codec_enc_create (K_CODECMUX_CODEC_CELP, &(prot->txcodecstate));
  if (n < 0)
    goto err_exit5;

  /* Config sio */
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
    sio = &(dseg->ldseg.hayes.sio);
  else
  {
    sio = &(dseg->ldseg.tram.sio);
    dseg->config.lcfg.tcfg.sio.porthnd = dseg->ldseg.tram.tapi.iochan;
    n = sio_open (sio, &(dseg->config.lcfg.tcfg.sio));
    if (n < 0)
      goto err_exit4;
  }
  n = sio_threads (sio, 1);
  if (n < 0)
    goto err_exit3;

  /* Create threads */
  prot->rxthread = CreateThread (NULL, 0, phone__rxthread, dseg, \
                                 CREATE_SUSPENDED, &(prot->rxthreadid));
  if (prot->rxthread == NULL)
  {
    n = E_PHONE_RXTHREAD;
    goto err_exit2;
  }
  SetThreadPriority (prot->rxthread, K_PHONE_DSPPRIO);
  prot->txthread = CreateThread (NULL, 0, phone__txthread, dseg, \
                                 CREATE_SUSPENDED, &(prot->txthreadid));
  if (prot->txthread == NULL)
  {
    TerminateThread (prot->rxthread, 0);
    n = E_PHONE_TXTHREAD;
    goto err_exit1;
  }
  SetThreadPriority (prot->txthread, K_PHONE_DSPPRIO);
  dseg->proto = prot;
  phone__linkexport (dseg, &(dseg->usrlink));
  cmd_notify (dseg->phone, K_CMD_NOTIFY_CONNECT_1, &(dseg->usrlink), 0);
  ResumeThread (prot->rxthread);
  ResumeThread (prot->txthread);

  /*** Monitor call ***/
  n = 0;
  har[0] = prot->rxthread;
  har[1] = prot->txthread;
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
#ifndef _WIN32_WCE
    WaitForMultipleObjects (2, har, FALSE, INFINITE);
#else
    while (42)
    {
#ifdef _WIN32_WCE
      SystemIdleTimerReset ();
#endif
      d = WaitForMultipleObjects (2, har, FALSE, K_PHONE_IDLERESTIME);
      if (d != WAIT_TIMEOUT)
        break;
    }
#endif
  else
    while (42)
    {
#ifdef _WIN32_WCE
      SystemIdleTimerReset ();
#endif
      n = tapi_watch (&(dseg->ldseg.tram.tapi), &(dseg->link.tapi));
      if (n < 0)
        break;
      n = 0;
      d = WaitForMultipleObjects (2, har, FALSE, 0L);
      if (d != WAIT_TIMEOUT)
        break;
    }

  /* Destroy threads */
  dseg->sighup = 1;
  cmd_notify (dseg->phone, K_CMD_NOTIFY_HANGUP, &(dseg->usrlink), 0);
  WaitForMultipleObjects (2, har, TRUE, INFINITE);
  EnterCriticalSection (&(dseg->plock[0]));
  EnterCriticalSection (&(dseg->plock[1]));
  dseg->proto = NULL;
  LeaveCriticalSection (&(dseg->plock[1]));
  LeaveCriticalSection (&(dseg->plock[0]));
  GetExitCodeThread (prot->txthread, &d);
  CloseHandle (prot->txthread);
  GetExitCodeThread (prot->rxthread, &e);
  err_exit1:
  CloseHandle (prot->rxthread);

  /* Restore sio */
  err_exit2:
  m = sio_threads (sio, 0);
  if ((m < 0) && (n >= 0))
    n = m;
  err_exit3:
  if (dseg->config.ltype == K_PHONE_LINETYPE_TAPI)
  {
    m = sio_close (sio);
    if ((m < 0) && (n >= 0))
      n = m;
  }

  /* Close codec */
  err_exit4:
  m = codec_enc_destroy (K_CODECMUX_CODEC_CELP, prot->txcodecstate);
  if ((m < 0) && (n >= 0))
    n = m;
  err_exit5:
  m = codec_dec_destroy (K_CODECMUX_CODEC_CELP, prot->rxcodecstate);
  if ((m < 0) && (n >= 0))
    n = m;

  /* Remove protocol */
  err_exit6:
  CloseHandle (prot->txudcevt);
  err_exit7:
  memset (prot, 0, sizeof (struct phone_protocol));
  HeapFree (heap, 0L, prot);
  err_exit8:
  m = audio_close (&(dseg->adseg));
  if ((m < 0) && (n >= 0))
    n = m;

  /* Hangup call */
  err_exit9:
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
    m = hayes_hangup (&(dseg->ldseg.hayes));
  else
    m = tapi_hangup (&(dseg->ldseg.tram.tapi));
  if ((m < 0) && (n >= 0))
    n = m;

  /* Exitcode */
  if (n >= 0)
  {
    n = (signed short)d;
    m = (signed short)e;
    if (m < n)
      n = m;
  }
  return n;
}

/*====*/

static void phone__linkexport (const struct phone_dseg *dseg, \
                               struct phone_link *usrlink)     /* Export Link */
{
  const unsigned char *p;
  unsigned char x;

  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
  {
    usrlink->dialout  = dseg->link.hayes.dialout;
    usrlink->service  = dseg->link.hayes.service;
    usrlink->carrier  = dseg->link.hayes.carrier;
    usrlink->protocol = dseg->link.hayes.protocol;
    usrlink->baudrate = dseg->link.hayes.baudrate;
    x = dseg->link.hayes.remnumlen;
    p = dseg->link.hayes.remnumbuf;
  }
  else
  {
    usrlink->dialout  = dseg->link.tapi.dialout;
    usrlink->service  = dseg->link.tapi.service;
    usrlink->carrier  = dseg->link.tapi.carrier;
    usrlink->protocol = 0xFF;
    usrlink->baudrate = dseg->link.tapi.baudrate;
    x = dseg->link.tapi.remnumlen;
    p = dseg->link.tapi.remnumbuf;
  }
  if (x >= K_PHONE_DIALNUMBUFLEN)
    x = K_PHONE_DIALNUMBUFLEN - 1;
  usrlink->diallength = x;
  if (x)
    memcpy (usrlink->dialstring, p, x);
  usrlink->dialstring[x] = 0;
}


static void phone__linkimport (struct phone_dseg *dseg, \
                               const struct phone_link *usrlink)
{                                                              /* Import Link */
  unsigned char x,*p;

  x = usrlink->diallength;
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
  {
    dseg->link.hayes.dialout  = usrlink->dialout;
    dseg->link.hayes.service  = usrlink->service;
    dseg->link.hayes.carrier  = usrlink->carrier;
    dseg->link.hayes.protocol = usrlink->protocol;
    dseg->link.hayes.baudrate = usrlink->baudrate;
    if (x >= K_HAYES_DIALNUMBUFLEN)
      x = K_HAYES_DIALNUMBUFLEN - 1;
    dseg->link.hayes.remnumlen = x;
    p = dseg->link.hayes.remnumbuf;
  }
  else
  {
    dseg->link.tapi.dialout  = usrlink->dialout;
    dseg->link.tapi.service  = usrlink->service;
    dseg->link.tapi.carrier  = usrlink->carrier;
    dseg->link.tapi.baudrate = usrlink->baudrate;
    if (x >= K_TAPI_DIALNUMBUFLEN)
      x = K_TAPI_DIALNUMBUFLEN - 1;
    dseg->link.tapi.remnumlen = x;
    p = dseg->link.tapi.remnumbuf;
  }
  if (x)
    memcpy (p, usrlink->dialstring, x);
  p[x] = 0;
}

/*----------------------------------------------------------------------------*/

/*------------*/
/*  RECEIVER  */
/*------------*/

static DWORD WINAPI phone__rxthread (LPVOID ptr)            /* Data-Rx Thread */
{
  struct phone_protocol *prot;
  struct phone_dseg *dseg;
  const unsigned char *q;
  struct sio_data *sio;
  unsigned char c,i,*p;
  signed short n;

  /* Pump speaker */
  dseg = ptr;
  prot = dseg->proto;
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
    sio = &(dseg->ldseg.hayes.sio);
  else
    sio = &(dseg->ldseg.tram.sio);
  EnterCriticalSection (&(dseg->plock[0]));
  while (42)
  {
    /* Receive data */
    do
    {
      LeaveCriticalSection (&(dseg->plock[0]));
      do
      {
        n = sio_getch (sio, 1, 1, 1, 1, &c);
      } while (!n);
      if (n < 0)
        return n;
      EnterCriticalSection (&(dseg->plock[0]));
      if (c == 0xFF)
        prot->rxsyncesc = 1;
    } while ((c == 0x11) || (c == 0x13) || (c == 0xFF));
    if (prot->rxsyncesc)
    {
      if (c == 0x00)
        c = 0xFF;
      else if (c == 0x01)
        c = 0x11;
      else if (c == 0x02)
        c = 0x13;
      prot->rxsyncesc = 0;
    }
    if (prot->rxsyncstat)
    {                            /* In Sync */
      if (prot->rxsyncidx == 0)        /* 0 */
      {
        if (c == 0x42)
          prot->rxsyncidx = 1;
        else
        {
          prot->rxsyncstat = 0;
          prot->rxchunklen = 0;
          cmd_notify (dseg->phone, K_CMD_NOTIFY_RXSYNC, &(prot->rxsyncstat), 0);
        }
      }
      else if (prot->rxsyncidx == 1)   /* 1 */
      {
        phone__rxsyncbyte (prot, c);
        prot->rxsyncidx = 2;
      }
      else
      {                            /* 2..65 */
        n = phone__rxdatabyte (dseg, c);
        if (n < 0)
          goto err_exit;
        if (prot->rxsyncidx < 65)
          prot->rxsyncidx++;
        else
          prot->rxsyncidx = 0;
      }
    }
    else
    {                            /* No Sync */
      if (prot->rxsyncidx == 0)        /* 0 */
      {
        if (c == 0x42)
          prot->rxsyncidx = 1;
      }                            /* 1..66 */
      else if (prot->rxsyncidx < 67)
      {
        prot->rxsyncbuf[prot->rxsyncidx - 1] = c;
        prot->rxsyncidx++;
      }
      else                            /* 67 */
      {
        prot->rxsyncbuf[66] = c;
        q = prot->rxsyncbuf;
        if ((q[65] == 0x42) && (((q[0]+1) & 0x0F) == (c & 0x0F)))
        {
          prot->rxsyncstat = 1;
          cmd_notify (dseg->phone, K_CMD_NOTIFY_RXSYNC, &(prot->rxsyncstat), 0);
          phone__rxsyncbyte (prot, *q++);
          for (i=0; i<64; i++)
          {
            n = phone__rxdatabyte (dseg, *q++);
            if (n < 0)
            {
              err_exit:
              LeaveCriticalSection (&(dseg->plock[0]));
              return n;
            }
          }
          phone__rxsyncbyte (prot, c);
          prot->rxsyncidx = 2;
        }
        else
        {
          for (i=0; i<67; i++)
            if (*q++ == 0x42)
              break;
          prot->rxsyncidx = 67 - i;
          if (prot->rxsyncidx > 1)
          {
            p = prot->rxsyncbuf;
            for (i=0; i<(prot->rxsyncidx-1); i++)
              *p++ = *q++;
          }
        }
      }
    }
  }
}

/*----*/

static void phone__rxsyncbyte (struct phone_protocol *prot, unsigned char data)
{                                                      /* Process Rx-SyncByte */
  unsigned char i,x,y,*p;

  /* Add sync-info */
  p = (unsigned char *)(prot->rxcryptcnt);
  x = data & 0x0F;
  *p = (*p & 0xC0) | (x << 2);
  if ((x & 0x01) == 0)
    y = 6;
  else
    y = 8 + (x << 1);
  x = data >> 4;
  for (i=0; i<2; i++)
  {
    p[y>>3] = (p[y>>3] & ~(0x03 << (y & 0x06))) | ((x & 0x03) << (y & 0x06));
    x >>= 2;
    y += 2;
  }
  prot->rxcryptbufpos = 0;
  crypto_tandem_encrypt (&(prot->rxcrypt), prot->rxcryptcnt, prot->rxcryptbuf);
}


static signed short phone__rxdatabyte (struct phone_dseg *dseg, \
                                       unsigned char data)
{                                                      /* Process Rx-DataByte */
  struct phone_protocol *prot;
  const unsigned char *q;
  unsigned char i,x,y,*p;
  const signed short *s;
  signed short n;
  DWORD d,e;

 /* Decrypt data-byte */
  prot = dseg->proto;
  q = (const unsigned char *)(prot->rxcryptbuf);
  x = q[prot->rxcryptbufpos] ^ data;
  if (prot->rxcryptbufpos < 15)
    prot->rxcryptbufpos++;
  else
  {
    prot->rxcryptbufpos = 0;
    p = (unsigned char *)(prot->rxcryptcnt);
    for (i=0; i<16; i++)
    {
      (*p)++;
      if (*p++)
        break;
    }
    crypto_tandem_encrypt (&(prot->rxcrypt), prot->rxcryptcnt, \
                           prot->rxcryptbuf);
  }
  /* Assemble block */
  prot->rxchunk[prot->rxchunklen++] = x;
  while (42)
  {
    /* interpret chunk */
    if (prot->rxchunklen < 3)
      break;
    if (prot->rxchunk[0] & 0x80)
      x = (prot->rxchunk[0] & 0x3F) + 3;
    else
      x = 20;
    if (prot->rxchunklen < x)
      break;
    crypto_sha_setup (&(prot->rxhash));
    crypto_sha_add   (&(prot->rxhash), prot->rxchunk, x - 1);
    crypto_sha_final (&(prot->rxhash), &y, 1);
    if (y == prot->rxchunk[x-1])
    { /* good hash */
      if (prot->rxsyncstat < 2)
      {
        prot->rxsyncstat = 2;
        cmd_notify (dseg->phone, K_CMD_NOTIFY_RXSYNC, &(prot->rxsyncstat), 0);
      }
      if ((prot->rxchunk[0] & 0x80) == 0)
      {
        /* Expand audio */
        y = (prot->rxchunk[0] & 0x40) >> 6;
        if (y != prot->rxcodecsel)
        {
          prot->rxcodecsel = y;
          cmd_notify (dseg->phone, K_CMD_NOTIFY_RXCODEC, &(prot->rxcodecsel), \
                      0);
        }
        d = prot->rxjittime;
        e = GetTickCount ();
        prot->rxjittime = e;
        e -= d;
        if (e <= K_PHONE_MAXJITTER)
        {
          if (e > prot->rxjitmax)
            prot->rxjitmax = (unsigned short)e;
          prot->rxjitcnt++;
          if (prot->rxjitcnt >= K_PHONE_JITTERCNT)
          {
            prot->rxjitcnt = 0;
            prot->rxjitval = prot->rxjitmax;
            prot->rxjitmax = 0;
jitter = prot->rxjitval;
wintop_notify (K_WINTOP_PARPHONENR);
          }
        }
        if (prot->rxmode)
        {
          d = 18;
          e = 240;
          n = codec_dec_data (6, prot->rxcodecstate, prot->rxchunk + 1, \
                              prot->rxaudio1, &d, &e);
          if (n < 0)
            return n;
          n = audio_outlen (&(dseg->adseg), &d);
          if (n < 0)
            return n;
          e = prot->rxjitval;
          e = (e << 3) + (e << 2) + 1600L;
          if (d < 800L)
          {
            if (d < 400L)
              y = 5;
            else
              y = 10;
            d = 0L;
            for (i=0; i<240; i++)
            {
              prot->rxaudio2[d++] = prot->rxaudio1[i];
              if (!(i % y))
                prot->rxaudio2[d++] = prot->rxaudio1[i];
            }
            s = prot->rxaudio2;
          }
          else if (d > e)
          {
            if (d > (e + (e << 1)))
              y = 3;
            else if (d > (e << 1))
              y = 5;
            else
              y = 10;
            d = 0L;
            for (i=0; i<240; i++)
              if (i % y)
                prot->rxaudio2[d++] = prot->rxaudio1[i];
            s = prot->rxaudio2;
          }
          else
          {
            d = 240L;
            s = prot->rxaudio1;
          }
          n = audio_output (&(dseg->adseg), s, &d, 1);
          if (n < 0)
            return n;
        }
      }
      else
      {
        /* Receive user-data */
        if ((prot->rxudclen + x) <= (K_PHONE_UDCMSGLEN + 6))
        {
          memcpy (prot->rxudcbuf + prot->rxudclen, prot->rxchunk + 1, x - 2);
          prot->rxudclen += (x - 2);
          if (prot->rxchunk[0] & 0x40)
          {
            if (prot->rxudclen > 4)
            {
              p = (unsigned char *)(&d);
              crypto_sha_setup (&(prot->rxhash));
              crypto_sha_add   (&(prot->rxhash), prot->rxudcbuf, \
                                prot->rxudclen - 4);
              crypto_sha_final (&(prot->rxhash), p, 4);
              q = prot->rxudcbuf + prot->rxudclen - 4;
              for (i=0; i<4; i++)
                if (*p++ != *q++)
                  break;
              if (i == 4)
                cmd_notify (dseg->phone, K_CMD_NOTIFY_RXUSERDATA, \
                            prot->rxudcbuf, (signed short)(prot->rxudclen - 4));
            }
            prot->rxudclen = 0;
          }
        }
        else
        {
          if (prot->rxchunk[0] & 0x40)
            prot->rxudclen = 0;
          else
            prot->rxudclen = K_PHONE_UDCMSGLEN + 4;
        }
      }
    }
    else
    { /* bad hash */
      if (prot->rxsyncstat > 1)
      {
        prot->rxsyncstat = 1;
        cmd_notify (dseg->phone, K_CMD_NOTIFY_RXSYNC, &(prot->rxsyncstat), 0);
      }
      x = 1;
    }
    p = prot->rxchunk;
    q = prot->rxchunk + x;
    for (i=x; i<prot->rxchunklen; i++)
      *p++ = *q++;
    prot->rxchunklen -= x;
  }
  return 0;
}

/*---------------*/
/*  TRANSMITTER  */
/*---------------*/

static DWORD WINAPI phone__txthread (LPVOID ptr)            /* Data-Tx Thread */
{
  struct phone_protocol *prot;
  struct phone_dseg *dseg;
  unsigned char h,w,x,y;
  struct sio_data *sio;
  unsigned long d,e;
  unsigned short z;
  signed short n;

  /* Pump microphone */
  dseg = ptr;
  prot = dseg->proto;
  if (dseg->config.ltype == K_PHONE_LINETYPE_HAYES)
    sio = &(dseg->ldseg.hayes.sio);
  else
    sio = &(dseg->ldseg.tram.sio);
  while (42)
  {
    /* Check line */
    n = sio_txcheck (sio, 1, 1, 1);
    if (n < 0)
      return n;

    /* Send audio */
    w = 1;
    x = 64;
    EnterCriticalSection (&(dseg->plock[1]));
    if (prot->txmode)
    {
      /* Read audio */
      d = 240;
      n = audio_inget (&(dseg->adseg), prot->txaudio, &d, 1);
      if (n < 0)
        goto err_exit;
      /* Compress audio */
      d = 240;
      e = 18;
      n = codec_enc_data (6, prot->txcodecstate,  prot->txaudio, \
                          prot->txchunk + 1, &d, &e);
      if (n < 0)
        goto err_exit;
      /* Transmit codec-data */
      prot->txchunk[0] = (prot->txcodecsel & 0x01) << 6;
      crypto_sha_setup (&(prot->txhash));
      crypto_sha_add   (&(prot->txhash), prot->txchunk, 19);
      crypto_sha_final (&(prot->txhash), prot->txchunk + 19, 1);
      n = phone__txchunk (sio, prot, 20);
      if (n < 0)
        goto err_exit;
      w = 0;
      x = 8;
    }
    LeaveCriticalSection (&(dseg->plock[1]));

    /* Send user-data */
    y = 0;
    while (42)
    {
      EnterCriticalSection (&(dseg->plock[1]));
      if (prot->txudcdada)
      {
        /* Generate udc-chunk */
        z = prot->txudclen[prot->txudcrdpos] - prot->txudcrdidx;
        if (z < x)
          y = (unsigned char)z;
        else
          y = x;
        memcpy (prot->txchunk + 1, prot->txudcbuf[prot->txudcrdpos] \
                + prot->txudcrdidx, y);
        prot->txudcrdidx += y;
        h = (y - 1) | 0x80;
        if (prot->txudcrdidx >= prot->txudclen[prot->txudcrdpos])
        {
          h |= 0x40;
          d = prot->txudcid[prot->txudcrdpos];
          prot->txudcrdidx = 0;
          if (prot->txudcrdpos < (K_PHONE_UDCMSGCNT - 1))
            prot->txudcrdpos++;
          else
            prot->txudcrdpos = 0;
          if (prot->txudcrdpos == prot->txudcwrpos)
          {
            prot->txudcdada = 0;
            ResetEvent (prot->txudcevt);
          }
        }
        /* Transmit udc-chunk */
        prot->txchunk[0] = h;
        crypto_sha_setup (&(prot->txhash));
        crypto_sha_add   (&(prot->txhash), prot->txchunk, 1 + y);
        crypto_sha_final (&(prot->txhash), prot->txchunk + 1 + y, 1);
        n = phone__txchunk (sio, prot, (unsigned char)(y + 2));
        if (n < 0)
        {
          err_exit:
          LeaveCriticalSection (&(dseg->plock[1]));
          return n;
        }
      }
      LeaveCriticalSection (&(dseg->plock[1]));
      if (y || !w)
        break;
      w = 0;
      WaitForSingleObject (prot->txudcevt, K_PHONE_TXCHECKTIME);
    }
    if (y && (h & 0x40))
      cmd_notify (dseg->phone, K_CMD_NOTIFY_TXUSERDATA, &d, 0);
  }
}

/*----*/

static signed short phone__txchunk (struct sio_data *sio,        \
                                    struct phone_protocol *prot, \
                                    unsigned char len)       /* Send Tx-Chunk */
{
  unsigned char c,i,j,x,y,z,*p;
  const unsigned char *q;
  signed short n;

  /* Send chunk */
  q = prot->txchunk;
  for (i=0; i<len; i++)
  {
    /* Add sync-info */
    p = (unsigned char *)(prot->txcryptcnt);
    if ((prot->txcryptbufpos == 0) && ((*p & 0x03) == 0))
    {
      n = sio_putch (sio, 1, 1, 1, 0, 0x42);
      if (n < 0)
        return n;
      x = (*p >> 2) & 0x0F;
      if ((x & 0x01) == 0)
        y = 6;
      else
        y = 8 + (x << 1);
      z = 0;
      for (j=0; j<2; j++)
      {
        c = p[y>>3];
        c = (c << (6 - (y & 0x06))) & 0xC0;
        z = (z >> 2) | c;
        y += 2;
      }
      x |= z;
      n = phone__txputch (sio, x);
      if (n < 0)
        return n;
    }
    /* Encrypt data */
    p = (unsigned char *)(prot->txcryptbuf);
    c = p[prot->txcryptbufpos] ^ *q++;
    n = phone__txputch (sio, c);
    if (n < 0)
      return n;
    if (prot->txcryptbufpos < 15)
      prot->txcryptbufpos++;
    else
    {
      prot->txcryptbufpos = 0;
      p = (unsigned char *)(prot->txcryptcnt);
      for (j=0; j<16; j++)
      {
        (*p)++;
        if (*p++)
          break;
      }
      crypto_tandem_encrypt (&(prot->txcrypt), prot->txcryptcnt, \
                             prot->txcryptbuf);
    }
  }
  n = sio_flush (sio, 1, 1, 1, 0);
  return n;
}


static signed short phone__txputch (struct sio_data *sio, unsigned char data)
{                                                             /* Send Tx-Byte */
  signed short n;

  /* Escape tx-byte */
  if ((data == 0x11) || (data == 0x13) || (data == 0xFF))
  {
    n = sio_putch (sio, 1, 1, 1, 0, 0xFF);
    if (n < 0)
      return n;
    else if (data == 0xFF)
      data = 0x00;
    else if (data == 0x11)
      data = 0x01;
    else if (data == 0x13)
      data = 0x02;
  }
  n = sio_putch (sio, 1, 1, 1, 0, data);
  return n;
}


/*** EOF ***/
